﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using AMM.Model;
using AMM.Model.Finance;

namespace AMM.DataAccess.Finance {
    public class StatementRepository : BaseRepository<Statement> {

        private int _pageSize;

        public StatementRepository(int pageSize = int.MaxValue) {
            this._pageSize = pageSize;
        }

        public Statement FetchStatementById(int statementId) {
            using (var ctx = this.Context) {
                return ctx.Statements
                    .Include("StatementDetails")
                    .FirstOrDefault(x => x.StatementId == statementId);
            }
        }

        public Statement FetchStatementByIdForView(int statementId) {
            using (var ctx = this.Context) {
                return ctx.Statements
                    .Include("StatementDetails")
                    .FirstOrDefault(x => x.StatementId == statementId);
            }
        }

        public void UpdateStatement(Statement statement) {
            using (var ctx = this.Context) {
                using (DbContextTransaction trans = ctx.Database.BeginTransaction()) {
                    try {
                        statement.StatementDetails.Where(x => x.ClassStatus == ClassStatus.Added).ToList().ForEach(x => {
                            x.Statement = statement;
                            x.StatementId = statement.StatementId;
                            x.StatementDetailsId = Guid.NewGuid().GetHashCode();
                        });
                        // do not try to merge with above codes, because you can't change the fields which was marked as primary key
                        statement.StatementDetails.ToList().ForEach(x => {
                            ctx.StatementDetails.Attach(x);
                            switch (x.ClassStatus) {
                                case ClassStatus.UnChanged:
                                    ctx.StatementDetails.Attach(x);
                                    ctx.Entry(x).State = EntityState.Unchanged;
                                    break;
                                case ClassStatus.Updated:
                                    ctx.StatementDetails.Attach(x);
                                    ctx.Entry(x).State = EntityState.Modified;
                                    break;
                                case ClassStatus.Added:
                                    ctx.StatementDetails.Attach(x);
                                    ctx.Entry(x).State = EntityState.Added;
                                    break;
                                case ClassStatus.Deleted:
                                    ctx.StatementDetails.Attach(x);
                                    ctx.Entry(x).State = EntityState.Deleted;
                                    break;
                            }
                        });

                        ctx.Statements.Attach(statement);
                        ctx.Entry(statement).State = EntityState.Modified;

                        ctx.SaveChanges();
                        trans.Commit();
                    }
                    catch (Exception ex) {
                        trans.Rollback();
                        throw ex;
                    }
                }
            }
        }

        public void DeleteMultipleStatement(params int[] statementIds) {
            using (var ctx = this.Context) {
                using (DbContextTransaction trans = ctx.Database.BeginTransaction()) {
                    try {
                        ctx.StatementDetails.Where(x => statementIds.Contains(x.StatementId)).ToList().ForEach(x => {
                            ctx.StatementDetails.Remove(x);
                        });
                        ctx.Statements.Where(x => statementIds.Contains(x.StatementId)).ToList().ForEach(x => {
                            ctx.Statements.Remove(x);
                        });
                        ctx.SaveChanges();
                        trans.Commit();
                    }
                    catch (Exception ex) {
                        trans.Rollback();
                        throw ex;
                    }
                }
            }
        }

        public List<Statement> FetchStatementList(StatementSearchModel searchModel, int pageIndex) {
            using (var ctx = this.Context) {
                var query = ctx.Statements
                    .Include("Vendor")
                    .Include("StatementDetails")
                    .Where(x => !string.IsNullOrEmpty(x.Code));

                if (!string.IsNullOrEmpty(searchModel.StatementCode)) {
                    query = query.Where(x => x.Code.Contains(searchModel.StatementCode));
                }
                if (searchModel.VendorId.HasValue) {
                    query = query.Where(x => x.VendorId == searchModel.VendorId.Value);
                }
                if (searchModel.Year.HasValue) {
                    query = query.Where(x => x.YearValue == searchModel.Year.Value);
                }
                if (searchModel.Month.HasValue) {
                    query = query.Where(x => x.MonthValue == searchModel.Month.Value);
                }
                if (searchModel.Status.HasValue) {
                    query = query.Where(x => x.Status == searchModel.Status.Value);
                }

                searchModel.Records = query.Count();
                pageIndex = (pageIndex - 1) * this._pageSize > searchModel.Records ? 1 : pageIndex;

                return query.OrderByDescending(x => x.CreatedDatetime).Skip((pageIndex - 1) * this._pageSize).Take(this._pageSize).ToList();
            }
        }

        public void GenerateStatement(string code, string createdBy, DateTime dt) {
            using (var ctx = this.Context) {
                using (DbContextTransaction trans = ctx.Database.BeginTransaction()) {
                    try {
                        int i = 1;
                        DateTime preDt = dt.AddMonths(-1);

                        List<ValueInAccountView> viavList = ctx.ValueInAccountViews
                            .Where(x => x.OrderCreatedDateTime.Year == dt.Year ||
                                        x.OrderCreatedDateTime.Year == preDt.Year)
                            .Where(x => x.OrderCreatedDateTime.Month == dt.Month && x.OrderCreatedDateTime.Day < 26 ||
                                        x.OrderCreatedDateTime.Month == preDt.Month && x.OrderCreatedDateTime.Day > 24)
                            .ToList();

                        List<Statement> sList = ctx.Statements
                            .Where(x => x.YearValue == DbFunctions.AddMonths(dt, -1).Value.Year)
                            .Where(x => x.MonthValue == DbFunctions.AddMonths(dt, -1).Value.Month)
                            .ToList();

                        viavList.GroupBy(x => x.VendorId, (key, list) => {
                            return new Statement() {
                                StatementId = Guid.NewGuid().GetHashCode(),
                                ClassStatus = ClassStatus.Added,
                                CreatedBy = createdBy,
                                CreatedDatetime = DateTime.Now,
                                VendorId = key,
                                Code = code.Substring(0, code.Length - 2),
                                LastSurplus = (sList.FirstOrDefault(z => z.VendorId == key) ?? new Statement()).Surplus,
                                NeededPay = list.Sum(z => z.Amount).Value,
                                Paid = 0,
                                YearValue = dt.Year,
                                MonthValue = dt.Month
                            };
                        }).ToList().ForEach(x => {
                            x.Code = string.Format("{0}{1}{2}", x.Code, i < 10 ? "0" : string.Empty, i++);
                            x.Surplus = x.LastSurplus + x.NeededPay - x.Paid;
                            viavList.Where(y => y.VendorId == x.VendorId)
                                .GroupBy(y => new { y.OrderCode, y.OrderCreatedDateTime, y.StoreType, y.Remarks }, (key, list) => {
                                    return new StatementDetail() {
                                        StatementDetailsId = Guid.NewGuid().GetHashCode(),
                                        ClassStatus = ClassStatus.Added,
                                        StatementId = x.StatementId,
                                        OrderCode = key.OrderCode,
                                        OrderCreatedDateTime = key.OrderCreatedDateTime,
                                        Amount = list.Sum(z => z.Amount.Value),
                                        StoreType = key.StoreType,
                                        Remarks = key.Remarks
                                    };
                                }).ToList().ForEach(y => {
                                    ctx.StatementDetails.Attach(y);
                                    ctx.Entry(y).State = EntityState.Added;
                                });

                            ctx.Statements.Attach(x);
                            ctx.Entry(x).State = EntityState.Added;
                        });
                        ctx.SaveChanges();
                        trans.Commit();
                    }
                    catch (Exception ex) {
                        trans.Rollback();
                        throw ex;
                    }
                }
            }
        }

        public bool CheckStatementPrerequisite(DateTime dt) {
            using (var ctx = this.Context) {
                DateTime preDt = dt.AddMonths(-1);
                var query = from a in ctx.SuspenseOrders
                                .Where(x => x.CreatedDatetime.Year == dt.Year ||
                                       x.CreatedDatetime.Year == preDt.Year)
                                .Where(x => x.CreatedDatetime.Month == dt.Month && x.CreatedDatetime.Day < 26 ||
                                       x.CreatedDatetime.Month == preDt.Month && x.CreatedDatetime.Day > 24)
                            join b in ctx.InStoreOrders on a.SuspenseOrderId equals b.SuspenseOrderId into bOutput
                            from b1 in bOutput.DefaultIfEmpty()
                            where b1.Code == null
                            select a;
                return query.ToList().Count > 0;
            }
        }

        public int GetTheMaxFlowNumber(string startString) {
            using (var ctx = this.Context) {
                Statement statement = ctx.Statements
                    .Where(x => x.Code.StartsWith(startString))
                    .OrderByDescending(x => x.Code)
                    .FirstOrDefault();
                int maxFlowNumber = statement == null
                    ? 0
                    : Convert.ToInt32(statement.Code.Substring(statement.Code.Length - 2, 2));
                return Math.Max(ctx.Statements.Count(x => x.Code.StartsWith(startString)), maxFlowNumber);
            }
        }
    }
}
